package com.frank.oj.manager;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.frank.model.dto.CompileDTO;
import com.frank.model.entity.judge.Judge;
import com.frank.model.entity.problem.Problem;
import com.frank.model.entity.problem.ProblemCase;
import com.frank.oj.common.exception.StatusFailException;
import com.frank.oj.common.exception.StatusForbiddenException;
import com.frank.oj.common.result.CommonResult;
import com.frank.oj.constant.Constants;
import com.frank.oj.core.dispatcher.Dispatcher;
import com.frank.oj.crawler.problem.ProblemStrategy;
import com.frank.oj.model.dto.ProblemDTO;
import com.frank.oj.service.entity.judge.JudgeEntityService;
import com.frank.oj.service.entity.problem.ProblemCaseEntityService;
import com.frank.oj.service.entity.problem.ProblemEntityService;
import com.frank.oj.shiro.AccountProfile;
import com.frank.oj.validator.ProblemValidator;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.io.File;
import java.util.List;

/**
 * @author frank
 * @Date 2024/4/2
 */
@Component
@RefreshScope
@Slf4j(topic = "hcoj")
public class AdminProblemManager {
    @Resource
    private ProblemEntityService problemEntityService;
    @Resource
    private ProblemCaseEntityService problemCaseEntityService;
    @Resource
    private ProblemValidator problemValidator;
    @Resource
    private RemoteProblemManager remoteProblemManager;
    @Resource
    private JudgeEntityService judgeEntityService;
    @Resource
    private Dispatcher dispatcher;

    @Value("${hcoj.judge.token}")
    private String judgeToken;

    public IPage<Problem> getProblemList(Integer limit, Integer currentPage, String keyword, Integer auth, String oj) {
        if (currentPage == null || currentPage < 1) currentPage = 1;
        if (limit == null || limit < 1) limit = 10;
        IPage<Problem> page = new Page<>(currentPage, limit);

        LambdaQueryWrapper<Problem> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Problem::getIsGroup, false)
                .orderByDesc(Problem::getId);

        if (oj != null && !"All".equals(oj)) {
            if (!Constants.RemoteOJ.isRemoteOJ(oj)) {
                queryWrapper.eq(Problem::getIsRemote, false);
            } else {
                queryWrapper.eq(Problem::getIsRemote, true).
                        likeRight(Problem::getProblemId, oj); //以远程oj名结尾
            }
        }

        if (auth != null && auth != 0) {
            queryWrapper.eq(Problem::getAuth, auth);
        }
        if (!StrUtil.isEmpty(keyword)) {
            String trim = keyword.trim();
            queryWrapper.and(wrapper -> wrapper.like(Problem::getTitle, trim).or().like(Problem::getProblemId, trim).or().like(Problem::getAuthor, trim));
        }
        return problemEntityService.page(page, queryWrapper);
    }

    public Problem getProblemById(Long pid) throws StatusForbiddenException, StatusFailException {
        Problem problem = problemEntityService.getById(pid);

        if (problem != null) {
            AccountProfile userRole = (AccountProfile) SecurityUtils.getSubject().getPrincipal();
            boolean root = SecurityUtils.getSubject().hasRole("root");
            boolean problemAdmin = SecurityUtils.getSubject().hasRole("problem_admin");
            // 仅超级管理员和题目管理员、题目创建者才能操作
            if (!root && !problemAdmin && !userRole.getUsername().equals(problem.getAuthor())) {
                throw new StatusForbiddenException("无权限");
            }
            return problem;
        } else {
            throw new StatusFailException("查询失败");
        }
    }

    public List<ProblemCase> getProblemCases(Long pid, Boolean isUpload) {

        LambdaQueryWrapper<ProblemCase> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ProblemCase::getPid, pid).eq(ProblemCase::getStatus, 0);

        if (isUpload) {
            queryWrapper.last("order by length(input) asc,input asc");
        }
        return problemCaseEntityService.list(queryWrapper);
    }

    public void addProblem(ProblemDTO problemDTO) throws StatusFailException {
        problemValidator.validateProblem(problemDTO.getProblem());

        LambdaQueryWrapper<Problem> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Problem::getProblemId, problemDTO.getProblem().getProblemId().toUpperCase());
        Problem problem = problemEntityService.getOne(queryWrapper);
        if (problem != null) {
            throw new StatusFailException("该题ID已存在，请换一个重试！");
        }

        boolean isOK = problemEntityService.adminAddProblem(problemDTO);
        if (!isOK) {
            throw new StatusFailException("添加失败");
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void importRemoteOJProblem(String name, String problemId) throws StatusFailException {
        LambdaQueryWrapper<Problem> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Problem::getProblemId, name.toUpperCase() + "-" + problemId);
        Problem problem = problemEntityService.getOne(queryWrapper);
        if (problem != null) {
            throw new StatusFailException(name.toUpperCase() + "的题【" + problemId + "】已存在，请勿重复添加！");
        }

        AccountProfile userRole = (AccountProfile) SecurityUtils.getSubject().getPrincipal();

        try {
            ProblemStrategy.RemoteProblemInfo otherOJProblemInfo = remoteProblemManager.getOtherOJProblemInfo(name.toUpperCase(), problemId, userRole.getUsername());
            if (otherOJProblemInfo != null) {
                Problem importProblem = remoteProblemManager.adminAddOtherOJProblem(otherOJProblemInfo, name);
                if (importProblem == null) {
                    throw new StatusFailException("导入新题目失败！请重新尝试！");
                }
            } else {
                throw new StatusFailException("导入新题目失败！原因：可能是与该OJ链接超时或题号格式错误！");
            }
        } catch (Exception e) {
            throw new StatusFailException(e.getMessage());
        }
    }

    @Transactional
    public void updateProblem(ProblemDTO problemDTO) throws StatusFailException, StatusForbiddenException {
        problemValidator.validateProblemUpdate(problemDTO.getProblem());

        AccountProfile userRole = (AccountProfile) SecurityUtils.getSubject().getPrincipal();
        boolean isRoot = SecurityUtils.getSubject().hasRole("root");
        boolean isProblemAdmin = SecurityUtils.getSubject().hasRole("problem_admin");
        // 只有超级管理员和题目管理员、题目创建者才能操作
        if (!isRoot && !isProblemAdmin && !userRole.getUsername().equals(problemDTO.getProblem().getAuthor())) {
            throw new StatusForbiddenException("对不起，你无权限修改题目！");
        }

        String problemId = problemDTO.getProblem().getProblemId().toUpperCase();
        LambdaQueryWrapper<Problem> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Problem::getProblemId, problemId);
        // problem存在的条件：要么未修改problemId，要么数据库中存在别的题使用了此problemId
        Problem problem = problemEntityService.getOne(queryWrapper); //如果为空则说明修改了problemId，所以数据库没有

        //修改了problemId则为空 或 未修改problemId且修改的题的id与查的id相等时不执行
        if (problem != null && problem.getId().longValue() != problemDTO.getProblem().getId()) {
            throw new StatusFailException("该题ID已存在，请换一个重试！");
        }
        problemDTO.getProblem().setModifiedUser(userRole.getUsername());

        boolean result = problemEntityService.adminUpdateProblem(problemDTO);

        if (result) {
            if (problem == null) {//说明改了problemId，同步一下judge表
                LambdaUpdateWrapper<Judge> updateWrapper = new LambdaUpdateWrapper<>();
                updateWrapper.eq(Judge::getPid, problemDTO.getProblem().getId())
                        .set(Judge::getDisplayPid, problemId);
                judgeEntityService.update(updateWrapper);
            }
        } else {
            throw new StatusFailException("修改失败");
        }

    }

    public void deleteProblem(Long pid) throws StatusFailException {
        boolean isOk = problemEntityService.removeById(pid);

        if (isOk) {
            FileUtil.del(Constants.File.TESTCASE_BASE_FOLDER.getPath() + File.separator + "problem_" + pid);
            AccountProfile userRole = (AccountProfile) SecurityUtils.getSubject().getPrincipal();
            log.info("[{}],[{}],pid:[{}],operatorUid:[{}],operatorUsername:[{}]",
                    "Admin_Problem", "Delete", pid, userRole.getUid(), userRole.getUsername());
        } else {
            throw new StatusFailException("删除失败");
        }
    }

    public void changeProblemAuth(Problem problem) throws StatusForbiddenException, StatusFailException {
        AccountProfile userRole = (AccountProfile) SecurityUtils.getSubject().getPrincipal();
        boolean root = SecurityUtils.getSubject().hasRole("root");
        boolean problemAdmin = SecurityUtils.getSubject().hasRole("problem_admin");

        if (!problemAdmin && !root && problem.getAuth() == 1) {
            throw new StatusForbiddenException("对不起，你无权限修改题目权限！");
        }

        LambdaUpdateWrapper<Problem> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(Problem::getId, problem.getId())
                .set(Problem::getAuth, problem.getAuth())
                .set(Problem::getModifiedUser, userRole.getUsername());
        boolean update = problemEntityService.update(updateWrapper);
        if (!update) {
            throw new StatusFailException("修改失败");
        }
        log.info("[{}],[{}],pid:[{}],operatorUid:[{}],operatorUsername:[{}]",
                "Admin_Problem", "Change_Problem_Auth", problem.getId(), userRole.getUid(), userRole.getUsername());
    }

    public CommonResult compileSpj(CompileDTO compileDTO) {
        if (StringUtils.isEmpty(compileDTO.getCode()) || StringUtils.isEmpty(compileDTO.getLanguage())) {
            return CommonResult.errorResponse("参数不能为空！");
        }

        compileDTO.setToken(judgeToken);
        return dispatcher.dispatch(Constants.TaskType.COMPILE_SPJ, compileDTO);
    }

    public CommonResult compileInteractive(CompileDTO compileDTO) {
        if (StringUtils.isEmpty(compileDTO.getCode()) ||
                StringUtils.isEmpty(compileDTO.getLanguage())) {
            return CommonResult.errorResponse("参数不能为空！");
        }

        compileDTO.setToken(judgeToken);
        return dispatcher.dispatch(Constants.TaskType.COMPILE_INTERACTIVE, compileDTO);
    }
}
